Разработка приложения Sushi App. Часть 1
➡️Ссылка на репозиторий с кодом этого урока (ветка main)
Добавление в корзину
Переходим в файл main_screen.dart и добавим ещё одно состояние rollsCart корзины для хранения данных в массиве.
Так же добавим метод добавления в корзину addToCart который передадим как колбэк виджету RollCard
Файл main_screen.dart
import 'package:flutter/material.dart';
import 'package:sushi_app/widgets/roll_card.dart';
import '../models/roll.dart';
import '../data/rolls_data.dart';
class MainScreen extends StatefulWidget {
MainScreen({super.key});
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
final List<Roll> rolls = data; // Данные для списка роллов
final List<Roll> rollsCart = []; // Список роллов в корзине
// Функция для добавления ролла в корзину
// После запускаем обновление UI через setState
void addToCart(Roll roll) {
setState(() {
rollsCart.add(roll);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sushi Shop'),
actions: <Widget>[
IconButton(
// Значение Badge теперь будет показывать количество данных в корзине
icon: Badge.count(
count: rollsCart.length,
child: const Icon(Icons.shopping_cart)
),
tooltip: 'Корзина',
// Чтобы Badge обновился при возврате назад на этот экран
onPressed: () async {
await Navigator.pushNamed(context, '/cart', arguments: rollsCart);
setState(() {});
},
),
],
),
body: GridView.builder(
padding: const EdgeInsets.symmetric(horizontal: 12),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, // Указываем количество колонок
crossAxisSpacing: 8.0, // Отступ между колонками
mainAxisSpacing: 8.0, // Отступ между строками
childAspectRatio: 0.85, // Соотношение сторон карточки
),
itemCount: rolls.length, // Количество элементов в списке
itemBuilder: (context, index) {
return RollCard(
roll: rolls[index],
// Передаем колбэк как параметр
onBuy: () => addToCart(rolls[index]), // Добавление ролла в корзину
);
},
),
);
}
}
Добавим в виджет RollCard новый параметр onBuy чтобы принять колбэк, и обработаем добавление в корзину при нажатии на кнопку Купить
Файл roll_card.dart
import 'package:flutter/material.dart';
import '../models/roll.dart';
import '../screens/detail_roll_screen.dart';
/// Виджет отображает информацию о ролле, переданной в параметре [roll].
/// Он отображает имя, описание, URL-адрес изображения и цену ролла.
///
class RollCard extends StatelessWidget {
final Roll roll;
final VoidCallback onBuy; // принимаем колбэк от родительского виджета
const RollCard({super.key, required this.roll, required this.onBuy});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () => Navigator.pushNamed(context, '/detail', arguments: roll),
child: Card(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ClipRRect(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
child: Image.asset(
roll.imageUrl,
width: double.infinity,
fit: BoxFit.cover,
),
),
),
Expanded(
child: ListTile(
contentPadding: EdgeInsets.zero,
title: Text(roll.name),
subtitle: Text(
roll.description,
style: const TextStyle(fontSize: 10),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("${roll.price} руб."),
TextButton(
onPressed: () {
onBuy(); // Вызываем функцию родительского виджета
},
child: const Text("Купить"),
),
],
),
],
),
),
),
);
}
}
Отображение корзины
Чтобы перейти в корзину добавим маршрут в таблицу маршрутизации в файле main.dart
В файле main_screen.dart обработаем нажатие на кнопку в AppBar для перехода на экран корзины.
Файл main.dart
import 'package:flutter/material.dart';
import '/screens/cart_screen.dart';
import '/screens/detail_roll_screen.dart';
import '/screens/main_screen.dart';
void main() => runApp(const RollApp());
/// Виджет RollApp - это точка входа в приложение по заказу роллов
/// Он создает MaterialApp, который является основой для
/// всех остальных виджетов.
class RollApp extends StatelessWidget {
const RollApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: "Суши Shop", // Название приложения
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), // Цветовая схема
),
initialRoute: '/', // Экран который видно самым первым
routes: {
'/': (context) => MainScreen(), // Главный экран
'/detail': (context) => DetailRollScreen(), // Экран детального просмотра
'/cart': (context) => CartScreen(), // Экран корзины
},
);
}
}
Файл main_screen.dart
IconButton(
icon: Badge.count(
count: rollsCart.length,
child: const Icon(Icons.shopping_cart)
),
tooltip: 'Корзина',
// Чтобы Badge обновился при возврате назад на этот экран
onPressed: () async {
await Navigator.pushNamed(context, '/cart', arguments: rollsCart);
setState(() {});
},
),
Добавим файл cart_screen.dart для экрана корзины
Получаем на этом экране ссылку на данные с экрана main_screen
Теперь изменения данных через ссылку rollsCart приведёт к изменению данных в переменной rollsCart на экране main_screen.dart
Файл cart_screen.dart
import 'package:flutter/material.dart';
import '../models/roll.dart';
class CartScreen extends StatefulWidget {
const CartScreen({super.key});
@override
State<CartScreen> createState() => _CartScreenState();
}
class _CartScreenState extends State<CartScreen> {
@override
Widget build(BuildContext context) {
// Получаем список роллов из аргументов
final List<Roll> rollsCart = ModalRoute.of(context)!.settings.arguments as List<Roll>;
// Вычисляем общую сумму
double totalPrice = 0.0;
for (var roll in rollsCart) {
totalPrice += roll.price;
}
return Scaffold(
appBar: AppBar(
title: const Text("Корзина"),
),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: rollsCart.length,
itemBuilder: (context, index) {
final roll = rollsCart[index];
return ListTile(
leading: Image.asset(roll.imageUrl, width: 50, height: 50, fit: BoxFit.cover),
title: Text(roll.name),
subtitle: Text('${roll.price.toStringAsFixed(2)} ₽'),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () {
setState(() {
rollsCart.removeAt(index); // Удаляем конкретный товар
});
},
),
);
},
),
),
const Divider(),
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text(
'Итого: ${totalPrice.toStringAsFixed(2)} ₽',
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: rollsCart.isEmpty ? null : () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Заказ оформлен!")),
);
},
child: const Text("Оформить заказ"),
),
],
),
),
],
),
);
}
}

.
